package org.wu.framework.lazy.database.datasource.proxy.connection;

import org.wu.framework.core.utils.ObjectUtils;
import org.wu.framework.lazy.database.datasource.proxy.handler.ConnectionHandler;
import org.wu.framework.lazy.database.datasource.proxy.handler.JdbcHandlerAdapter;
import org.wu.framework.lazy.database.datasource.proxy.sql.LazySQLContext;

import javax.sql.DataSource;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.sql.Connection;
import java.sql.Statement;

/**
 * @author : Jia wei Wu
 * @version 1.0
 * describe : 数据库连接 代理对象
 * @date : 2020/8/28 下午10:43
 */
public class LazyConnectionInvocationHandler implements InvocationHandler {
    // 当前数据源
    private final DataSource targetDataSource;
    // 当前 连接
    private final Connection targetConnection;

    private final JdbcHandlerAdapter jdbcHandlerAdapter;

    public LazyConnectionInvocationHandler(DataSource targetDataSource, Connection targetConnection, JdbcHandlerAdapter jdbcHandlerAdapter) {
        this.targetDataSource = targetDataSource;
        this.targetConnection = targetConnection;
        this.jdbcHandlerAdapter = jdbcHandlerAdapter;
    }


    /**
     * Processes a method invocation on a proxy instance and returns
     * the result.  This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     *
     * @param proxy  the proxy instance that the method was invoked on
     * @param method the {@code Method} instance corresponding to
     *               the interface method invoked on the proxy instance.  The declaring
     *               class of the {@code Method} object will be the interface that
     *               the method was declared in, which may be a superinterface of the
     *               proxy interface that the proxy class inherits the method through.
     * @param args   an array of objects containing the values of the
     *               arguments passed in the method invocation on the proxy instance,
     *               or {@code null} if interface method takes no arguments.
     *               Arguments of primitive types are wrapped in instances of the
     *               appropriate primitive wrapper class, such as
     *               {@code java.lang.Integer} or {@code java.lang.Boolean}.
     * @return the value to return from the method invocation on the
     * proxy instance.  If the declared return type of the interface
     * method is a primitive type, then the value returned by
     * this method must be an instance of the corresponding primitive
     * wrapper class; otherwise, it must be a type assignable to the
     * declared return type.  If the value returned by this method is
     * {@code null} and the interface method's return type is
     * primitive, then a {@code NullPointerException} will be
     * thrown by the method invocation on the proxy instance.  If the
     * value returned by this method is otherwise not compatible with
     * the interface method's declared return type as described above,
     * a {@code ClassCastException} will be thrown by the method
     * invocation on the proxy instance.
     * @throws Throwable the exception to throw from the method
     *                   invocation on the proxy instance.  The exception's type must be
     *                   assignable either to any of the exception types declared in the
     *                   {@code throws} clause of the interface method or to the
     *                   unchecked exception types {@code java.lang.RuntimeException}
     *                   or {@code java.lang.Error}.  If a checked exception is
     *                   thrown by this method that is not assignable to any of the
     *                   exception types declared in the {@code throws} clause of
     *                   the interface method, then an
     *                   {@link UndeclaredThrowableException} containing the
     *                   exception that was thrown by this method will be thrown by the
     *                   method invocation on the proxy instance.
     * @see UndeclaredThrowableException
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {


        try {
            // 确定 ConnectionHandler
            ConnectionHandler connectionHandler = jdbcHandlerAdapter.findFirstConnectionHandler();

            String name = method.getName();
            if (name.equals("isConnectionTransactional")) {
                // 判断是否有事物
                return connectionHandler.isConnectionTransactional(targetDataSource, (Connection) proxy);
            }
            if (isConnectionCloseMethod(method)) {
                // 断开连接
                connectionHandler.connectionClose(targetDataSource, (Connection) proxy);
                return Void.TYPE;
            }

            // set Auto commit
            if (isSetAutoCommitMethod(method, args)) {
                connectionHandler.setAutoCommit(targetDataSource, targetConnection, (Boolean) args[0]);
                return Void.TYPE;
            }

            // get auto commit
            if (isGetAutoCommitMethod(method, args)) {
                return connectionHandler.getAutoCommit(targetDataSource, targetConnection);
            }

            // commit
            if (isCommitMethod(method, args)) {
                connectionHandler.commit(targetDataSource, targetConnection);
                return Void.TYPE;
            }
            // rollback
            if (isRollbackMethod(method, args)) {
                connectionHandler.rollback(targetDataSource, targetConnection);
                return Void.TYPE;
            }

            long startTime = System.currentTimeMillis();
            Object invoke = method.invoke(targetConnection, args);
            long endTime = System.currentTimeMillis();

//        System.out.printf("当前方法: %s 执行时间:%s(毫秒) %n", method, endTime - startTime);

            // 统计执行时间
            // sql审计
            if (jdbcHandlerAdapter.findLazyAuditAdapter() != null) {
                if (invoke instanceof Statement) {
                    LazySQLContext lazySQLContext = new LazySQLContext();
                    lazySQLContext.setBeginTime(startTime);
                    lazySQLContext.setEndTime(endTime);
                    if (invoke != null) {
                        lazySQLContext.setSql(invoke.toString());
                        jdbcHandlerAdapter.findLazyAuditAdapter().audit(lazySQLContext);
                    }
                }

            }
            // statement 代理拦截执行的sql

            return invoke;
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            throw throwable;
        }
    }

    /**
     * 是否是 rollback 方法
     *
     * @param method 代理的方法
     * @param args   参数
     * @return rollback 方法
     */
    protected boolean isRollbackMethod(Method method, Object[] args) {
        return method.getName().equals("rollback") && ObjectUtils.isEmpty(args);
    }

    /**
     * 是否是 commit 方法
     *
     * @param method 代理的方法
     * @param args   参数
     * @return commit 方法
     */
    protected boolean isCommitMethod(Method method, Object[] args) {
        return method.getName().equals("commit") && ObjectUtils.isEmpty(args);
    }

    /**
     * 是否是 getAutoCommit 方法
     *
     * @param method 代理的方法
     * @param args   参数
     * @return getAutoCommit 方法
     */
    protected boolean isGetAutoCommitMethod(Method method, Object[] args) {
        return method.getName().equals("getAutoCommit");
    }

    /**
     * 是否是 setAutoCommit 方法
     *
     * @param method 代理的方法
     * @param args   参数
     * @return setAutoCommit 方法
     */
    protected boolean isSetAutoCommitMethod(Method method, Object[] args) {
        return method.getName().equals("setAutoCommit");
    }

    /**
     * 是否是 connectionClose 方法
     *
     * @param method 代理的方法
     * @return connectionClose 方法
     */
    protected boolean isConnectionCloseMethod(Method method) {
        return method.getName().equals("connectionClose") && method.getParameterCount() == 0;
    }


    /**
     * 创建连接的代理对象
     *
     * @param dataSource 数据源
     * @param connection 连接
     * @param <A>
     * @return
     */
    public static <A extends Connection> A createProxy(DataSource dataSource, Connection connection) {

        return createProxy(dataSource, connection, new JdbcHandlerAdapter());
    }

    /**
     * 创建连接的代理对象
     *
     * @param <A>
     * @param targetDataSource   数据源
     * @param targetConnection   连接
     * @param jdbcHandlerAdapter 数据源适配器
     * @return
     */
    public static <A extends Connection> A createProxy(DataSource targetDataSource,
                                                       Connection targetConnection,
                                                       JdbcHandlerAdapter jdbcHandlerAdapter) {
        ClassLoader classLoader = LazyProxyConnection.class.getClassLoader();
        Class<?>[] interfaces = new Class<?>[]{LazyProxyConnection.class};
        InvocationHandler handler = new LazyConnectionInvocationHandler(targetDataSource, targetConnection, jdbcHandlerAdapter);
        return (A) Proxy.newProxyInstance(classLoader, interfaces, handler);
    }

}
